#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include "string.h"
#include <sys/wait.h>
#include "Estacionamiento.h"
#include "concurrencia/Cola.h"
#include "PlazaEstacionamiento.h"
#include "Simulacion.h"
#include "Estructuras.h"
#include "Entrada.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <sstream>
#include "Utils.h"
#include "concurrencia/SimulacionHandler.h"
#include "concurrencia/SignalHandler.h"
#include "Salida.h"

using namespace std;
void crearProcesoGeneral(int cantidadEstacionamiento, int cantidadLugares,
		double precioPorHora);
pid_t crearProcesoEstacionamiento(int numeroEstacionamiento);
void crearProcesoUsuario(int cantidadEstacionamientos, int cantidadLugares);
int main(int argc, char* argv[]) {
	/*
	 * Tenemos tres colas:
	 * 		1 - Para la comunicacion con el proceso principal, poniendo "mtype == 1",
	 * 			al cual se le van a hacer las siguientes consultas:
	 * 				a - Lugar a ocupar en un estacionamiento
	 * 				b - Informar la salida de un vehiculo
	 * 				c - Plata recaudada de un estacionamiento
	 * 				d - Lugares disponibles de un estacionamiento
	 * 			El mismo va a responder poniendo "mtype == procId del que mando la consulta"
	 *		2 - Para comunicar con la entrada poniendo "mtype == numero del estacionamiento"
	 *		3 - Para comunicar con la salida poniendo "mtype == numero del estacionamiento"
	 *
	 * */
	if (argc != 5) {
		cout << "Cantidad incorrecta de parámetros para la simulación." << endl
				<< "Uso: " << endl
				<< "	TpEstacionamiento [cantidad estacionamientos] [cantidad lugares por estacionamiento] [precio/hora] [tiempo simulacion]"
				<< endl;
		exit(0);
	}
	int cantidadEstacionamiento = atoi(argv[1]);
	int cantidadLugares = atoi(argv[2]);
	double precioPorHora = atoi(argv[3]);
	int tiempoEspera = atoi(argv[4]);
	//Creo la carpeta donde se van a guardar los archivos para luego borrar
	mkdir("./tmp", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);

	//Creo los archivos de las colas
	int fd = open(Utils::NOMBRE_ARCHIVO_COLA_PROCESO_PRINCIPAL,
			O_CREAT | O_WRONLY, 0777);
	close(fd);
	fd = open(Utils::NOMBRE_ARCHIVO_ENTRADA, O_CREAT | O_WRONLY, 0777);
	close(fd);
	fd = open(Utils::NOMBRE_ARCHIVO_SALIDA, O_CREAT | O_WRONLY, 0777);
	close(fd);
	fd = open(Utils::NOMBRE_ARCHIVO_PARA_LOG, O_CREAT | O_WRONLY, 0777);
	close(fd);
	fd = open(Utils::NOMBRE_ARCHIVO_MEM_ESTACIONAMIENTO, O_CREAT | O_WRONLY,
			0777);
	close(fd);

	LockFile lock((char *) Utils::NOMBRE_ARCHIVO_PARA_LOG);
	string mensaje = "INICIO SIMULACION\n";
	char* log = (char*) mensaje.c_str();
	lock.escribirConLock(log, strlen(log));

	pid_t procId = fork();
	if (procId == 0) {
		for (int i = 0; i < cantidadEstacionamiento; i++) {
			if (crearProcesoEstacionamiento(i) == 0) {
				return 0;
			}
		}
		return 0;
	} else {
		pid_t procId = fork();
		if (procId == 0) {
			procId = fork();
			if (procId == 0) {
				Simulacion simulacion(cantidadEstacionamiento);
				simulacion.empezar();
			} else {
				sleep(tiempoEspera);
				kill(procId, SIGINT);
			}

		} else {
			pid_t procId = fork();
			if (procId == 0) {
				crearProcesoGeneral(cantidadEstacionamiento, cantidadLugares,
						precioPorHora);
			} else {
				crearProcesoUsuario(cantidadEstacionamiento, cantidadLugares);
				return 0;
			}
		}
	}
	return 0;
}

pid_t crearProcesoEstacionamiento(int numeroEstacionamiento) {
	LockFile lock((char *) Utils::NOMBRE_ARCHIVO_PARA_LOG);
	string mensaje = "Creando Estacionamiento: "
			+ to_string(numeroEstacionamiento) + "\n";
	char* log = (char*) mensaje.c_str();
	lock.escribirConLock(log, strlen(log));

	//Para que devuelva este y saber si fue le padre o el hijo que esta volviendo
	pid_t procIdInicial = fork();
	if (procIdInicial == 0) {
		pid_t procId = fork();
		if (procId == 0) {
			mensaje = "Creo las entradas para el estacionamiento : "
					+ to_string(numeroEstacionamiento) + "\n";
			log = (char*) mensaje.c_str();
			lock.escribirConLock(log, strlen(log));
			procId = fork();
			if (procId == 0) {
				procId = fork();
				if (procId == 0) {
					Entrada entrada(1, numeroEstacionamiento);
					entrada.iniciar();
				} else {
					Entrada entrada(2, numeroEstacionamiento);
					entrada.iniciar();
				}
			} else {
				Entrada entrada(0, numeroEstacionamiento);
				entrada.iniciar();
			}
		} else {
			mensaje = "Creo las salidas para el estacionamiento : "
					+ to_string(numeroEstacionamiento) + "\n";
			log = (char*) mensaje.c_str();
			lock.escribirConLock(log, strlen(log));
			procId = fork();
			if (procId == 0) {
				Salida salida(0, numeroEstacionamiento);
				salida.iniciar();
			} else {
				Salida salida(1, numeroEstacionamiento);
				salida.iniciar();
			}
		}
	}
	return procIdInicial;
}

void crearProcesoUsuario(int cantidadEstacionamientos, int cantidadLugares) {
	int opcion = 0;
	int nroEst = 0;
	bool terminado = false;
	SimulacionHandler admSim;
	SignalHandler::getInstance()->registrarHandler(SIGINT, &admSim);

	Cola<mensaje> cola(Utils::NOMBRE_ARCHIVO_COLA_PROCESO_PRINCIPAL, 'A');
	mensaje peticion;
	Cola<autoSerializable> salida(Utils::NOMBRE_ARCHIVO_SALIDA, 'A');

	peticion.mtype = MTYPE_PROCESO_GENERAL;
	peticion.mtypeProximo = getpid();

	while (!terminado) {
		cout << "Escriba numero de estacionamiento a consultar" << endl;
		cin >> nroEst;
		if (nroEst >= cantidadEstacionamientos) {
			cout << "Error en numero de estacionamiento" << endl;
			cout << "Hay " << cantidadEstacionamientos
					<< " estacionamientos y empieza a contar en el 0" << endl;
			continue;
		}
		cout << "Elija una opcion" << endl;
		cout << "1 - Plata" << endl;
		cout << "2 - Autos" << endl;
		cout << "3 - Terminar" << endl;
		cin >> opcion;
		switch (opcion) {
		case 1: {
			mensaje respuesta;
			cout << "1 - Plata" << endl;
			peticion.tipoMensaje = 3;
			peticion.numeroEstacionamiento = nroEst;
			int res = cola.escribir(peticion);
			if (res == -1) {
				cout << "Error a realizar peticion";
			}
			res = cola.leer(getpid(), &respuesta);
			if (res == -1) {
				cout << "Error a obtener respuesta";
			}
			cout << "Cantidad de plata recaudada: " << respuesta.plataRecaudada
					<< endl;
			break;
		}
		case 2: {
			mensaje respuesta;
			cout << "2 - Autos" << endl;
			peticion.tipoMensaje = 4;
			peticion.numeroEstacionamiento = nroEst;
			int res = cola.escribir(peticion);
			if (res == -1) {
				cout << "Error a realizar peticion";
			}
			res = cola.leer(getpid(), &respuesta);
			if (res == -1) {
				cout << "Error a obtener respuesta";
			}
			cout << "Cantidad de autos: " << respuesta.lugarDisponible << endl;
			break;
		}
		case 3: {
			mensaje respuesta;
			terminado = true;
			for (int i = 0; i < cantidadEstacionamientos; i++) {
				peticion.tipoMensaje = 4;
				peticion.numeroEstacionamiento = i;
				int res = cola.escribir(peticion);
				if (res == -1) {
					cout << "Error a realizar peticion";
				}
				res = cola.leer(getpid(), &respuesta);
				if (res == -1) {
					cout << "Error a obtener respuesta";
				}
				if (respuesta.lugarDisponible != 0) {
					cout << "Hay " << respuesta.lugarDisponible
							<< " que aun no salieron del estacionamiento " << i
							<< ". Aguarde por favor." << endl;
					terminado = false;
				}
			}
			if (terminado) {
				cout << "Terminando" << endl;
				peticion.tipoMensaje = TIPO_MENSAJE_FIN;
				cola.escribir(peticion);
				//termino las salidas
				for (int i = 1; i <= cantidadEstacionamientos; i++) {
					for (int j = 0; j < 2; j++) {
						autoSerializable autoSerializable;
						autoSerializable.mtype = i;
						autoSerializable.tipoMensaje = TIPO_MENSAJE_FIN;
						int resultado = salida.escribir(autoSerializable);
						if (resultado == -1) {
							cout
									<< "Error al escribir mensaje de fin a entrada de estacionamiento: "
									<< i << endl;
						}
					}
				}
				salida.destruir();
			}
			break;
		}
		default:
			break;
		}
	}
}

void crearProcesoGeneral(int cantidadEstacionamiento, int cantidadLugares,
		double precioPorHora) {
	SimulacionHandler simulacionHandler;
	SignalHandler::getInstance()->registrarHandler(SIGINT, &simulacionHandler);
	MemoriaCompartida<Estacionamiento> estacionamientos;
	estacionamientos.crear(Utils::NOMBRE_ARCHIVO_MEM_ESTACIONAMIENTO, 'A',
			cantidadEstacionamiento);
	Cola<mensaje> cola(Utils::NOMBRE_ARCHIVO_COLA_PROCESO_PRINCIPAL, 'A');

	for (int i = 0; i < cantidadEstacionamiento; i++) {
		Estacionamiento est(i, cantidadLugares, precioPorHora);
		estacionamientos.escribir(i, est);
	}
	mensaje peticion;
	mensaje respuesta;
	int procId = 5;
	bool terminar = false;
	LockFile lock((char *) Utils::NOMBRE_ARCHIVO_PARA_LOG);
	string mensaje = "Inicio Proceso General \n";
	char* log = (char*) mensaje.c_str();
	lock.escribirConLock(log, strlen(log));
	while (!terminar) {
		int resultado = cola.leer(MTYPE_PROCESO_GENERAL, &peticion);
		mensaje = "Llego peticion : " + to_string(peticion.tipoMensaje)
				+ " de user: " + to_string(peticion.mtypeProximo) + "\n";
		log = (char*) mensaje.c_str();
		lock.escribirConLock(log, strlen(log));
		if (resultado != -1) {
			procId = fork();
			if (procId == 0) {
				//Ve que peticion tiene y que hace con el mensaje
				terminar = true;
				respuesta.mtype = peticion.mtypeProximo;
				respuesta.mtypeProximo = MTYPE_PROCESO_GENERAL;
				respuesta.tipoMensaje = peticion.tipoMensaje;
				switch (peticion.tipoMensaje) {
				case 1: {
					//Consultar si hay lugar
					Estacionamiento* estacionamiento = estacionamientos.leer(
							peticion.numeroEstacionamiento);
					respuesta.lugarDisponible =
							estacionamiento->getPosicionLibre();
					if (respuesta.lugarDisponible != -1) {
						Auto a;
						estacionamiento->estaciona(&a);
						mensaje = "Lugar a ocupar del auto"
								+ to_string(respuesta.lugarDisponible) + "\n";
						log = (char*) mensaje.c_str();
						lock.escribirConLock(log, strlen(log));
					}
					int res = cola.escribir(respuesta);
					if (res == -1) {
						cout << "Error al escribir respuesta" << endl;
					}
					break;
				}
				case 2: {
					//Informar salida de un auto
					mensaje = "Sale auto del estacionamiento : "
							+ to_string(peticion.numeroEstacionamiento)
							+ "con posicion"
							+ to_string(peticion.posicionOcupada) + "y tiempo"
							+ to_string(peticion.tiempoEnElEstacionamiento)
							+ "\n";
					log = (char*) mensaje.c_str();
					lock.escribirConLock(log, strlen(log));
					Estacionamiento* estacionamiento = estacionamientos.leer(
							peticion.numeroEstacionamiento);
					estacionamiento->sacarAuto(peticion.posicionOcupada,
							peticion.tiempoEnElEstacionamiento);
					break;
				}
				case 3: {
					//Consultar Plata recaudada
					Estacionamiento* estacionamiento = estacionamientos.leer(
							peticion.numeroEstacionamiento);
					respuesta.plataRecaudada =
							estacionamiento->getAdministrador()->consultarDineroRecaudado();
					int res = cola.escribir(respuesta);
					if (res == -1) {
						cout << "Error al escribir respuesta" << endl;
					}
					break;
				}
				case 4: {
					//Consultar cantidad autos en el estacionamiento
					Estacionamiento* estacionamiento = estacionamientos.leer(
							peticion.numeroEstacionamiento);
					respuesta.lugarDisponible =
							estacionamiento->getAdministrador()->consultarCantidadAutos();
					int res = cola.escribir(respuesta);
					if (res == -1) {
						cout << "Error al escribir respuesta" << endl;
					}
					break;
				}
				}
			} else if (peticion.tipoMensaje == 5) {
				terminar = true;
			}
		} else {
			cout << "Error al recibir mensaje" << endl;
		}

	}

	for (int i = 0; i < cantidadEstacionamiento; i++) {
		estacionamientos.leer(i)->liberarMemoria();
		if (procId != 0) {
			estacionamientos.leer(i)->getAdministrador()->terminar();
		}
	}
	estacionamientos.liberar();
	if (procId != 0) {
		mensaje = "Terminando proceso general \n";
		log = (char*) mensaje.c_str();
		lock.escribirConLock(log, strlen(log));
		cola.destruir();
		system("rm -r ./tmp/");
		system("ipcs");
	}
	return;

}

